from machine import Pin, PWM, Timer
from utime import sleep_ms

BRIGHTNESS_NONE = const(0)
BRIGHTNESS_LOW = const(10)
BRIGHTNESS_MEDIUM = const(50)
BRIGHTNESS_HIGH = const(200)

# ACTION_NORMAL = const(-1)
ACTION_ON = const(0)
ACTION_OFF = const(1)


class  LedPadException(Exception):
	pass


class LedBase(object):
	def __init__(self, io, pwm=True):
		if pwm:
			self.__pin = PWM(Pin(io, Pin.OUT, value=0), freq=100000, duty=0)
		else:
			self.__pin = Pin(io, Pin.OUT, value=0)
		
		self.__pwm = pwm
		self.__brightness = BRIGHTNESS_MEDIUM
		self.__action = ACTION_OFF
		self.__state = False
	
	def deinit(self):
		if self.__pwm:
			self.__pin.deinit()
		else:
			self.__pin = None

	def set_brightness(self, level):
		self.__brightness = level
	
	def set_state(self, state: bool):
		if self.__state == state:
			return
		
		self.__state = state
		self.__action = ACTION_ON if self.__state else ACTION_OFF

	def on(self):
		if self.__pwm:
			self.__pin.duty(self.__brightness)
		else:
			self.__pin.off()
	
	def off(self):
		if self.__pwm:
			self.__pin.duty(0)
		else:
			self.__pin.on()

	@property
	def action(self):
		return self.__action

	@property
	def state(self):
		return self.__state

class LedPad(object):
	"""
	5*4 LED 矩阵驱动
	"""
	# MATRIX_SCAN_PERIOD = 50 # 键盘矩阵扫描间隔

	def __init__(self, pin_set=None):
		assert pin_set is not None and isinstance(pin_set, tuple) and len(pin_set) == 2, LedPadException("pin_set must be a tuple, e.g. ((row output io), (column input io))")

		self.__row_io_set = []
		self.__column_io_set = []

		try:
			for io in pin_set[0]:
				self.__row_io_set.append(LedBase(io))
			
			for io in pin_set[1]:
				self.__column_io_set.append(LedBase(io, False))
		except IndexError:
			raise LedPadException("pin_set value error")

		self.__row_count = len(self.__row_io_set)
		self.__column_count = len(self.__column_io_set)

		assert self.__row_count > 0 and self.__column_count > 0, LedPadException("pin_set value error")

		self.__brightness = BRIGHTNESS_HIGH
		# self.__rows_off()
		# self.__columns_off()
		self.__timer = Timer(14)

		self.__timer.init(
			mode=Timer.PERIODIC,
			period=50,
			callback=self.__timer_cb
		)

	def __timer_cb(self, timer):
		for led_row in self.__row_io_set:
			for led_column in self.__column_io_set:
				if led_row.state and led_column.state:
					led_row.on()
					led_column.on()
				elif led_row.state and not led_column.state:
					led_row.on()
					led_column.off()
				elif not led_row.state and led_column.state:
					led_row.off()
					led_column.on()
				else:
					led_row.off()
					led_column.off()
			# 	led_column.off()
			# led_row.off()
				# if not led_row.state and led_row.action == ACTION_OFF:
				# 	led_row.off()
				# elif not led_column.state and led_column.action == ACTION_OFF:
				# 	led_column.off()
				# else:
				# 	led_row.off()
				# 	led_column.off()
			# led.on() if led.action == ACTION_ON else led.off()
			# led.on() if led.action == ACTION_OFF else led.off()

	def deinit(self):
		self.__timer.deinit()

		for io in self.__row_io_set:
			io = None
		for io in self.__column_io_set:
			io.deinit()
		
		self.__timer = None
		self.__row_io_set = None
		self.__column_io_set = None

	def set_brightness(self, brightness=BRIGHTNESS_MEDIUM):
		self.__brightness = brightness

	def get_led_count(self):
		"""
		获取 Led 数量
		"""
		return self.__row_count * self.__column_count

	def __rows_on(self):
		for io in self.__row_io_set:
			io.duty(self.__brightness)

	def __rows_off(self):
		for io in self.__row_io_set:
			io.duty(0)

	def __row_on(self, index):
		self.__row_io_set[index].duty(self.__brightness)

	def __row_off(self, index):
		self.__row_io_set[index].duty(0)

	def __columns_on(self):
		for io in self.__column_io_set:
			io.off()

	def __columns_off(self):
		for io in self.__column_io_set:
			io.on()

	def __column_on(self, index):
		self.__column_io_set[index].off()

	def __column_off(self, index):
		self.__column_io_set[index].on()

	def __clean(self, sleep=200):
		# self.__rows_off()
		self.__columns_off()
		sleep_ms(sleep)

	def __all_on(self):
		self.__rows_on()
		self.__columns_on()

	def check(self):
		for index_row in range(self.__row_count):
			self.__rows_off()
			self.__row_on(index_row)

			if index_row % 2 == 0:
				for io in self.__column_io_set:
					io.off()
					sleep_ms(200)
					io.on()
			else:
				for io in reversed(self.__column_io_set):
					io.off()
					sleep_ms(200)
					io.on()

		self.__rows_off()

	def light_on(self, row, column, sleep=200):
		# self.__row_on(row)
		# self.__column_on(column)
		self.__row_io_set[row].set_state(True)
		self.__column_io_set[column].set_state(True)
		# sleep_ms(50)

	def light_off(self, row, column):
		self.__row_io_set[row].set_state(False)
		self.__column_io_set[column].set_state(False)
		# self.__row_off(row)
		# self.__column_off(column)

	def lights_up(self):
		self.__clean()
		self.__rows_on()
		self.__columns_on()
		sleep_ms(2000)
		self.__clean()
	
	def blink_all(self):
		self.__clean()

		for i in range(5):
			self.__rows_on()
			self.__columns_on()
			sleep_ms(300)
			self.__clean()

		self.__clean()


def main():
	ROW_SET = (13, 12, 14, 27, 26) # for output
	COLUMN_SET = (25, 33, 32) # for intput
	PIN_SET = (ROW_SET, COLUMN_SET)

	ledpad = LedPad(PIN_SET)
	print("LedPad led count:", ledpad.get_led_count())

	# ledpad.check()
	# ledpad.lights_up()

	# ledpad.set_brightness(BRIGHTNESS_MEDIUM)
	# ledpad.check()
	# ledpad.lights_up()

	# ledpad.set_brightness(BRIGHTNESS_LOW)
	# ledpad.check()
	# ledpad.lights_up()

	# ledpad.blink_all()
	ledpad.light_on(0, 0)
	ledpad.light_on(3, 2)
	# sleep_ms(2000)
	# ledpad.light_off(1, 1)

	# ledpad.light_on(1, 1)
	# ledpad.light_on(2, 2)
	# ledpad.light_on(3, 1)
	# ledpad.light_on(4, 0)

	# ledpad.deinit()
	# ledpad = None


if __name__ == "__main__":
	try:
		main()
	except KeyboardInterrupt:
		print("\nPRESS CTRL+D TO RESET DEVICE")
